﻿// <copyright file="MostPopular.cs" company="Microsoft Corporation">
// Copyright (c) 2010 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2010-09-13</date>
// <summary>CLR Class for accessing New York Times Most Popular API</summary>

namespace NYTimes
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Windows;
    using System.Xml.Linq;
    using NYTimes.Core;
    using NYTimes.Data;

    /// <summary>
    /// Most Popular Resource Type
    /// </summary>
    public enum MostPopularResourceType
    {
        /// <summary>
        /// Most emailed articles
        /// </summary>
        MostEmailed,

        /// <summary>
        /// Most shared articles
        /// </summary>
        MostShared,

        /// <summary>
        /// Most viewed articles
        /// </summary>
        MostViewed
    }

    /// <summary>
    /// New York Times Most Popular API
    /// </summary>
    /// <remarks>
    /// API Key: NYTMostPopularKey</remarks>
    public class MostPopular : INotifyPropertyChanged, IDisposable
    {
        #region Fields
        /// <summary>
        /// Application resources key
        /// </summary>
        public const string NYTMostPopularKey = "NYTMostPopularKey";

        /// <summary>
        /// API Version
        /// </summary>
        private const string Version = "v2";

        /// <summary>
        /// Resource type
        /// </summary>
        private MostPopularResourceType resourceType;

        /// <summary>
        /// Sections to return
        /// </summary>
        private string section = "all-sections";

        /// <summary>
        /// Time period (1, 7, or 30 days)
        /// </summary>
        private int timePeriod;

        /// <summary>
        /// The API Key
        /// </summary>
        private string apiKey;

        /// <summary>
        /// The share types
        /// </summary>
        private string shareTypes;

        /// <summary>
        /// The current web client
        /// </summary>
        private WebClient webClient;

        /// <summary>
        /// the result offset
        /// </summary>
        private int offset;

        /// <summary>
        /// the API Status
        /// </summary>
        private string status;

        /// <summary>
        /// The copyright message
        /// </summary>
        private string copyright;

        /// <summary>
        /// The total number of results
        /// </summary>
        private int numResults;

        /// <summary>
        /// The Maximum number of results to return
        /// </summary>
        private int maxResults;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the MostPopular class.
        /// </summary>
        public MostPopular()
        {
            this.Results = new ObservableCollection<MostPopularItem>();

            this.MaxResults = 20;

            if (Application.Current.Resources.Contains(NYTMostPopularKey))
            {
                this.ApiKey = Application.Current.Resources[NYTMostPopularKey] as string;
            }

            if (SampleData.IsInDesignTool)
            {
                var xml = SampleData.GetSampleData("MostPopular.xml");

                if (!string.IsNullOrEmpty(xml))
                {
                    this.Parse(xml);
                }
            }
        }

        #endregion

        #region Events
        /// <summary>
        /// The Property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Input Properties

        /// <summary>
        /// Gets or sets the resource type
        /// </summary>
        /// <remarks>Input</remarks>
        public MostPopularResourceType ResourceType
        {
            get
            {
                return this.resourceType;
            }

            set
            {
                if (this.resourceType != value)
                {
                    this.resourceType = value;

                    this.Offset = 0;
                }
            }
        }

        /// <summary>
        /// Gets or sets the section
        /// </summary>
        /// <remarks>Input</remarks>
        public string Section 
        { 
            get
            {
                return this.section;
            }

            set
            {
                if (this.section != value)
                {
                    this.section = value;

                    this.Offset = 0;
                }
            }
        }

        /// <summary>
        /// Gets or sets the time period in days (1, 7, or 30)
        /// </summary>
        /// <remarks>Input</remarks>
        public int TimePeriod
        {
            get
            {
                return this.timePeriod;
            }

            set
            {
                if (this.timePeriod != value)
                {
                    if (value == 1 || value == 7 || value == 30)
                    {
                        this.timePeriod = value;

                        this.Offset = 0;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the API Key
        /// </summary>
        /// <remarks>Input</remarks>
        public string ApiKey 
        { 
            get
            {
                return this.apiKey;
            }

            set
            {
                if (this.apiKey != value)
                {
                    this.apiKey = value;

                    this.Offset = 0;
                }
            }
        }

        /// <summary>
        /// Gets or sets the share types
        /// </summary>
        /// <remarks>Input</remarks>
        public string ShareTypes
        {
            get
            {
                return this.shareTypes;
            }

            set
            {
                if (this.shareTypes != value)
                {
                    this.shareTypes = value;

                    this.Offset = 0;
                }
            }
        }

        /// <summary>
        /// Gets the offset (in increments of 20)
        /// </summary>
        /// <remarks>Output</remarks>
        public int Offset 
        { 
            get
            {
                return this.offset;
            }

            private set
            {
                if (this.offset != value)
                {
                    this.offset = value;
                }

                this.Status = "Loading";

                this.FetchData();
            }
        }

        /// <summary>
        /// Gets or sets the maximum number of results to retrieve
        /// </summary>
        /// <remarks>Input</remarks>
        public int MaxResults
        {
            get
            {
                return this.maxResults;
            }

            set
            {
                if (this.maxResults != value)
                {
                    if (value < this.maxResults)
                    {
                        while (this.Results.Count > value)
                        {
                            this.Results.Remove(this.Results.Last());
                        }

                        this.maxResults = value;
                    }
                    else
                    {
                        this.maxResults = value;

                        this.Offset = 0;
                    }
                }
            }
        }

        #endregion

        #region Output Properties
        /// <summary>
        /// Gets the API Status
        /// </summary>
        /// <remarks>Output</remarks>
        public string Status
        {
            get
            {
                return this.status;
            }

            private set
            {
                if (this.status != value)
                {
                    this.status = value;

                    this.TriggerPropertyChanged("Status");
                }
            }
        }

        /// <summary>
        /// Gets the copyright
        /// </summary>
        /// <remarks>Output</remarks>
        public string Copyright
        {
            get
            {
                return this.copyright;
            }

            private set
            {
                if (this.copyright != value)
                {
                    this.copyright = value;

                    this.TriggerPropertyChanged("Copyright");
                }
            }
        }

        /// <summary>
        /// Gets the number of results
        /// </summary>
        /// <remarks>Output</remarks>
        public int TotalResults
        {
            get
            {
                return this.numResults;
            }

            private set
            {
                if (this.numResults != value)
                {
                    this.numResults = value;

                    this.TriggerPropertyChanged("TotalResults");
                }
            }
        }

        /// <summary>
        /// Gets the results
        /// </summary>
        /// <remarks>Output</remarks>
        public ObservableCollection<MostPopularItem> Results { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Dispose of the resources
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }
        #endregion

        #region Implementation

        /// <summary>
        /// Dispose of the web client
        /// </summary>
        /// <param name="managedAndNativeResources">dispose of both the managed and native resources</param>
        protected virtual void Dispose(bool managedAndNativeResources)
        {
            if (this.webClient != null)
            {
                this.webClient.Dispose();

                this.webClient = null;
            }
        }

        /// <summary>
        /// Try to parse the DateTime from the element value
        /// </summary>
        /// <param name="element">an XML Element</param>
        /// <returns>the DateTime or the current date/time</returns>
        private static DateTime GetDateTime(XElement element)
        {
            DateTime time;

            if (DateTime.TryParse(element.Value, out time))
            {
                return time;
            }

            return DateTime.Now;
        }

        /// <summary>
        /// Trigger the property changed event handler
        /// </summary>
        /// <param name="propertyName">the property name</param>
        private void TriggerPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Fetch the data
        /// </summary>
        private void FetchData()
        {
            if (SampleData.IsInDesignTool)
            {
                return;
            }

            if (this.offset == 0)
            {
                this.Results.Clear();
            }

            if (string.IsNullOrEmpty(this.ApiKey))
            {
                return;
            }

            if (this.TimePeriod < 1)
            {
                return;
            }

            var address = this.GenerateUri();

            System.Diagnostics.Debug.WriteLine(address);

            if (this.webClient != null)
            {
                this.webClient.CancelAsync();
            }

            this.webClient = new WebClient();

            this.webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnDownloadStringCompleted);

            this.webClient.DownloadStringAsync(address);
        }

        /// <summary>
        /// XML Download has completed
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                return;
            }

            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine(e.Error);

                return;
            }

            this.Parse(e.Result);
        }

        /// <summary>
        /// Parse the XML results
        /// </summary>
        /// <param name="xml">the XML results</param>
        private void Parse(string xml)
        {
            var doc = XDocument.Parse(xml);

            var resultSet = doc.Root;

            var results = from result in resultSet.Element("results").Elements("result")
                          select new MostPopularItem
                          {
                              Url = new Uri(result.Element("url").Value, UriKind.Absolute),
                              Column = result.Element("column").Value,
                              Section = result.Element("section").Value,
                              Byline = result.Element("byline").Value,
                              Title = result.Element("title").Value,
                              Abstract = result.Element("abstract").Value,
                              PublishedDate = GetDateTime(result.Element("published_date")),
                              Source = result.Element("source").Value,
                              DescriptionFacets = new Collection<string>((from des_facet_item in result.Element("des_facet").Elements("des_facet_item")
                                                  select des_facet_item.Value).ToList()),
                              OrgFacets = new Collection<string>((from org_facet in result.Element("org_facet").Elements("org_facet_item")
                                          select org_facet.Value).ToList()),
                              PerFacets = new Collection<string>((from per_facet in result.Element("per_facet").Elements("per_facet_item")
                                          select per_facet.Value).ToList()),
                              GeoFacets = new Collection<string>((from geo_facet in result.Element("geo_facet").Elements("geo_facet_item")
                                          select geo_facet.Value).ToList()),
                              Media = new Collection<MostPopularMediaItem>((from media_item in result.Element("media").Elements("media_item")
                                      select new MostPopularMediaItem
                                      {
                                          MediaType = media_item.Element("type").Value,
                                          Subtype = media_item.Element("subtype").Value,
                                          Caption = media_item.Element("caption").Value,
                                          Copyright = media_item.Element("copyright").Value,
                                          Metadata = new Collection<MediaMetadata>((from media_metadata_item in media_item.Element("media-metadata").Elements("media-metadata_item")
                                          select new MediaMetadata
                                          {
                                              Url = new Uri(media_metadata_item.Element("url").Value, UriKind.Absolute),
                                              Format = media_metadata_item.Element("format").Value,
                                              Height = int.Parse(media_metadata_item.Element("height").Value, CultureInfo.InvariantCulture),
                                              Width = int.Parse(media_metadata_item.Element("width").Value, CultureInfo.InvariantCulture),
                                          }).ToList())
                                      }).ToList())
                          };

            foreach (var item in results)
            {
                if (this.Results.Count >= this.MaxResults)
                {
                    break;
                }

                this.Results.Add(item);
            }

            this.Copyright = resultSet.Element("copyright").Value;

            this.TotalResults = int.Parse(resultSet.Element("num_results").Value, CultureInfo.InvariantCulture);

            if (this.Results.Count < this.MaxResults && this.Results.Count < this.TotalResults)
            {
                this.Offset += 20;
            }
            else
            {
                this.Status = resultSet.Element("status").Value;
            }
        }

        /// <summary>
        /// Generate a URI
        /// </summary>
        /// <returns>the Most Popular service URL</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "API takes lowercase characters.")]
        private Uri GenerateUri()
        {
            var builder = new StringBuilder("http://api.nytimes.com/svc/mostpopular/");

            builder.AppendFormat(
                CultureInfo.InvariantCulture, 
                "{0}/{1}/{2}",
                Version,
                this.ResourceType.ToString().ToLowerInvariant(),
                this.Section);

            if (this.ResourceType == MostPopularResourceType.MostShared && !string.IsNullOrEmpty(this.ShareTypes))
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "/{0}",
                    this.ShareTypes);
            }

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "/{0}.xml?api-key={1}",
                this.TimePeriod,
                this.ApiKey);

            if (this.Offset > 0)
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "&offset={0}",
                    this.Offset);
            }

            var uri = new Uri(builder.ToString(), UriKind.Absolute);

            return uri;
        }
    }
#endregion
}
